package com.example.springboottest.student.service.impl;

import cn.hutool.json.JSONUtil;
import com.example.springboottest.async.service.AsyncTestService;
import com.example.springboottest.base.AbstractService;
import com.example.springboottest.example.xml.digester.DigesterMain;
import com.example.springboottest.student.mapper.StuMapper;
import com.example.springboottest.student.model.Student;
import com.example.springboottest.student.service.Stu2MybatisService;
import com.example.springboottest.student.service.StuMybatisService;
import com.example.springboottest.student.service.TempService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.apache.ibatis.cursor.Cursor;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

@Service("stuMybatisService")
public class StuMybatisServiceImpl extends AbstractService implements StuMybatisService {
    @Autowired
    private StuMapper stuMapper;
    @Autowired
    private Stu2MybatisService stu2MybatisService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private AsyncTestService asyncTestService;
    Logger logstashLogger = LoggerFactory.getLogger("org.springframework.core.KotlinDetector");
    @Autowired
    private KieContainer kieContainer;

    private final String REDIS_STU_PRE = "redisCache::redis_stu_";

    @HystrixCommand(
            fallbackMethod = "doFallbackFindALl",
            commandProperties = {
                    @HystrixProperty(
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "1000")
            })
    @Override
    public List<Student> doFindALl() {
        List<Student> list = stuMapper.findAll();
//        asyncTestService.doLongTimeWork();
        logger.info("info...");
        logger.debug("debug...");
        logger.warn("warn...");
        logger.error("error...");
        logstashLogger.info("测试下..............");

//        int i = 10/0;
        return list;
    }

    /**
     * 备胎
     *
     * @return
     */
    public List<Student> doFallbackFindALl() {
        List<Student> list = new ArrayList<>();
        list.add(new Student("No000001", "默认用户", 18, "默认地址"));
        logstashLogger.info("in doFallbackFindALl function");
        return list;
    }

    /**
     * @CacheEvict 的作用是删除缓存。在执行方法前，删除缓存。
     * 注解@CacheEvict 主要用来清除缓存，可以指定的属性有 value、key、condition、allEntries 和
     * beforeInvocation。其中 value、key 和 condition 的语义与@Cacheable 对应的属性类似。value 表示清
     * 除哪些 Cache（对应 Cache 的名称）；key 表示清除哪个 Key 键；condition 表示清除的条件。
     * （1）allEntries 属性：表示是否全部清空
     * @CacheEvict(value = "userCache", allEntries = true)
     * userCache需要先在类上注解 @CacheConfig(cacheNames = "userCache")
     */
    @Override
    public void doDeleteById(String id) {

    }

    /**
     * @Caching 注解，是一个缓存处理的组合注解。通过@Caching，可以一次指定多个 Spring Cache
     * 注解的组合。@Caching 注解拥有三个属性：cacheable、put 和 evict。
     * 一个方法上，一次性加上三大类 cache 处理
     * @Caching(cacheable = @Cacheable(key = "'userCache:'+ #uid"),
     * put = @CachePut(key = "'userCache:'+ #uid"),
     * evict = {
     * @CacheEvict(key = "'userCache:'+ #uid"),
     * @CacheEvict(key = "'addressCache:'+ #uid"),
     * @CacheEvict(key = "'messageCache:'+ #uid") } )
     * public void  test(String uid) {
     * }
     */

    @Override
    public void doCreate(Student student) {
        stuMapper.create(student);
//        doUpdateCache(student);
    }

    @Override
    public void doUpdate(Student student) {
//        idoUpdate(student);
//        student.setAddress("111");
        stuMapper.update(student);

//        testDrool(student);
//        stu2MybatisService.idoUpdate();
//        int i = 9 / 0;
        doUpdateCache(student);
    }

    private void testDrool(Student student) {
        KieSession kieSession = kieContainer.newKieSession();
//        kieSession.setGlobal("student", student);
        kieSession.insert(student);
        kieSession.fireAllRules();
        kieSession.dispose();
    }

    /**
     * 不想修改doCreate和doUpdate接口的返回类型
     * 直接手动设置redis缓存
     *
     * @param student
     */
    /**@CachePut 作用是设置缓存。先执行方法，并将执行结果缓存起来
     * redisCache名称来源于application.yml下面的配置
     *   cache:
     *     type: redis
     *     cache-names: redisCache
     */
//    @CachePut(value = "redisCache", condition = "#student != 'null'", key = "'redis_stu_'+#student.id")
    public void doUpdateCache(Student student) {
//        redisTemplate.opsForValue().set(REDIS_STU_PRE + student.getId(), student);
        //为了和nginx-redis的格式一致采用的纯json格式
//        String dd= (String) redisTemplate.opsForValue().get(REDIS_STU_PRE + student.getId());
        redisTemplate.opsForValue().set(REDIS_STU_PRE + student.getId(), JSONUtil.parse(student).toString());
    }

    /**
     * RedisConnection 连接类和 RedisTemplate 模板类都提供了整套 Redis 操作的 API，
     * 只不过，它们的层次不同。RedisConnection 连接类更加底层，它负责二进制层面的 Redis 操作，
     * Key、Value 都是二进制字节数组。而 RedisTemplate 模板类，在 RedisConnection 的基础上，使用
     * 在 spring-redis.xml 中配置的序列化、反序列化的工具类，完成上层类型（如 String、Object、POJO
     * 类等）的 Redis 操作
     * 如果不需要 RedisTemplate 配置的序列化、反序列化的工具类，或者由于其他的原因，需要直
     * 接使用 RedisConnection 去操作 Redis，怎么办呢？可以使用 RedisCallback 的 doInRedis 回调方法，
     * 在 doInRedis 回调方法中，直接使用实参 RedisConnection 连接类实例来完成 Redis 的操作
     *
     * @param student
     */
    public void doUpdateCache_bak(Student student) {
        redisTemplate.execute(new RedisCallback<Student>() {
            public Student doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] key = serializeKey(student.getId());
                connection.set(key, serializeValue(student));
                connection.expire(key, 10);
                return student;
            }
        });
    }

    private byte[] serializeValue(Student s) {
        return redisTemplate.getValueSerializer().serialize(s);
    }

    private byte[] serializeKey(String s) {
        return redisTemplate.getKeySerializer().serialize(s);
    }

    private Student deSerializeValue(byte[] b) {
        return (Student) redisTemplate.getValueSerializer().deserialize(b);
    }

    @Override
    public void idoUpdate(Student student) {
        Student otherStudent = stuMapper.findById("7f798baea8f845419e2b5bbecbb54ecc");
        otherStudent.setAddress("上海市1111");
        stuMapper.update(otherStudent);
    }

    @Override
    /**@Cacheable 的作用更多是查询缓存。首先检查注解中的 Key 键是否在缓存中，
    如果是则返回 Key 的缓存值，不再执行方法；
    否则，执行方法并将方法结果缓存起来。
    从后半部分来看，@Cacheable 也具备@CachePut 的能力
     */
    //@Cacheable 也具备@CachePut 的能力。
//    @Cacheable(value = "redisCache", key = "'redis_stu_'+#id")
    //nginx-redis和spring-redis的存储格式不一样，只能选用一个
    public Student doFindById(String id) {
//        Student student = (Student) redisTemplate.opsForValue().get(REDIS_STU_PRE + id);
//        if (!Objects.isNull(student))
//            return student;
//        student = stuMapper.findById(id);
//        redisTemplate.opsForValue().set(REDIS_STU_PRE + id, student);
//        return student;
        return stuMapper.findById(id);
    }

    @Override
    public void doUpdateLock(Student student) {

    }

    @Override
    public Page<Student> doPage() {
        return null;
    }

    @Override
    public String doLock() {
        return "stop";
    }

    @Override
    public List<Student> doFindALlByCrusor() {
        Cursor<Student> cursor = stuMapper.doFindUserByCrusor();
        List<Student> list = new ArrayList<>();
        Iterator<Student> it = cursor.iterator();
        while (it.hasNext()) {
            Student student = it.next();
            student.setAddress("----");
            stuMapper.update(student);
            list.add(student);
        }
        return list;
    }
}
